package gbif.common.dal;



import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.EntityMode;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;



/**
 * Data access object (DAO) for domain model
 * Implementation of  IBaseDAO for Hibernate and Spring Hubernate-DAO Support
 * @author Federico Mendez - GBIF
 */
@Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class,readOnly=false)
public class HibernateBaseDAO<T,K extends Serializable>  extends HibernateDaoSupport implements IBaseDAO<T,K>, Serializable {	
	/**
	 *serialVersionUID 
	 */
	private static final long serialVersionUID = -2958539076159892925L;
	static final Log log = LogFactory.getLog(HibernateBaseDAO.class);
	
	Class<T> type;


	public HibernateBaseDAO(){
		super();
	}
	
	public HibernateBaseDAO(Class<T> type){
		super();
		this.type = type;
	}
	
	public Class<T> getType() {
		return type;
	}

	public void setType(Class<T> type) {
		this.type = type;
	}

	public void save(T transientInstance) {
		log.debug("saving  instance");
		try {
			getHibernateTemplate().save(transientInstance);
			log.debug("save successful");
		} catch (RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}
	}

	public void delete(T persistentInstance) {
		log.debug("deleting  instance");
		try {
			getHibernateTemplate().delete(persistentInstance);
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.NOT_SUPPORTED)
	public T findById(K id) {
		log.debug("getting instance with id: " + id);
		try {
			T instance = (T) getHibernateTemplate().get(this.type, id);
			return instance;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.NOT_SUPPORTED)
	public List<T> findByExample(T instance) {
		log.debug("finding instance by example");
		try {
			List<T> results = getHibernateTemplate().findByExample(instance);
			log.debug("find by example successful, result size: "
					+ results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.NOT_SUPPORTED)
	public List<T> findByProperty(String propertyName, Object value) {
		log.debug("finding  instance with property: " + propertyName
				+ ", value: " + value);
		try {
			String queryString = "from " + this.type.getName() +  " as model where model."
					+ propertyName + "= ?";
			return getHibernateTemplate().find(queryString, value);
		} catch (RuntimeException re) {
			log.error("find by property name failed", re);
			throw re;
		}
	}
	
	
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.REQUIRED)
	public int bulkUpdateByProperty(String propertyName, Object value, Operation operation) {
		log.debug("bulk operation (" + operation.getStatementName() + ") on instance with property: " + propertyName
				+ ", value: " + value);
		try {
			String queryString = operation.getStatementName() + " from " + this.type.getName() +  " as model where model."
					+ propertyName + " = ?";
			return getHibernateTemplate().bulkUpdate(queryString, new Object[]{value});
		} catch (RuntimeException re) {
			log.error("bulk operation by property name failed", re);
			throw re;
		}
	}
	
	
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.REQUIRED)
	public int bulkUpdateByProperties(String propertiesNames[], Object values[], Operation operation) {
		log.debug("bulk operation (" + operation.getStatementName() + ") on instance with properties: " + Arrays.toString(propertiesNames)
				+ ", values: " + Arrays.toString(values));
		try {
			StringBuffer queryString = new StringBuffer(operation.getStatementName() + " from " + this.type.getName() + " as model where ");
			for(int i = 0; i < propertiesNames.length; i++){
				queryString.append( i>0 ? " and model." + propertiesNames[i] + " = ? " : " model." + propertiesNames[i] + " = ? ");
			}			
			return getHibernateTemplate().bulkUpdate(queryString.toString(), values);
		} catch (RuntimeException re) {
			log.error("bulk operation by properties names failed", re);
			throw re;
		}
	}


	@SuppressWarnings("unchecked")
	@Transactional(rollbackFor=Exception.class,readOnly=true,propagation=Propagation.NOT_SUPPORTED)
	public List<T> findAll() {
		log.debug("finding all T instances");
		try {
			String queryString = "from " + this.type.getName();
			return getHibernateTemplate().find(queryString);
		} catch (RuntimeException re) {
			log.error("find all failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	public T merge(T detachedInstance) {
		log.debug("merging T instance");
		try {
			T result = (T)getHibernateTemplate().merge(detachedInstance);
			log.debug("merge successful");
			return result;
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}
	
	public void update(T detachedInstance) {
		log.debug("update T instance");
		try {
			getHibernateTemplate().update(detachedInstance);
			log.debug("update successful");
		} catch (RuntimeException re) {
			log.error("update failed", re);
			throw re;
		}
	}

	public void attachDirty(T instance) {
		log.debug("attaching dirty Actividad instance");
		try {
			getHibernateTemplate().saveOrUpdate(instance);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}

	public void attachClean(T instance) {
		log.debug("attaching clean T instance");
		try {
			getHibernateTemplate().lock(instance, LockMode.NONE);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}
	
	public Session getCurrentSession(){
		return this.getHibernateTemplate().getSessionFactory().getCurrentSession();
	}
	
	

	

	
	//Utility methods
	
	@SuppressWarnings("rawtypes")
	public void evictAll(Collection result){		
		try {

			if ((result != null) && (!result.isEmpty())){
				for(Iterator itCollection = result.iterator(); itCollection.hasNext();){
					Object evitado = itCollection.next();//get the last element
					this.evictObject(evitado);
				}
			}			
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void evictObject(Object evicted) {
		try {
			if(evicted !=null){			
			Class c = evicted.getClass(); //Get the superclass of the object to be evicted
			Method method;
			org.hibernate.metadata.ClassMetadata cM = this.getHibernateTemplate().getSessionFactory().getClassMetadata(c);	
			if(cM == null){
				c = c.getSuperclass();
				cM = this.getHibernateTemplate().getSessionFactory().getClassMetadata(c);
				evicted = c.cast(evicted); 
			}
			if(cM != null){				
				Object idValue = cM.getIdentifier(evicted,EntityMode.POJO); //entity object ID field
				this.getSession().evict(evicted); // take the object out of the session cache
				String idFieldName = this.getHibernateTemplate().getSessionFactory().getClassMetadata(c).getIdentifierPropertyName();
				
				Class[] parameterTypes = null;
				Method methods[] = c.getMethods();
				String setMethodName = "set"+ idFieldName.substring(0,1).toUpperCase() + idFieldName.substring(1);
				boolean found= false;
				for(int i=0; (i < methods.length) && (!found); i++){
					Method m = methods[i];
					if(m.getName().equals(setMethodName)){
						parameterTypes = m.getParameterTypes();
						found = true;
					}
				}
				if(found){
					method = c.getMethod(setMethodName,parameterTypes);
					Object[] arguments = new Object[] {idValue};
					method.invoke(evicted,arguments);
				}
			}
		 }
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}
	
	@SuppressWarnings("rawtypes")
	public void setStateByReflection(Object oPersistente) {
		try {
				
				Class c = oPersistente.getClass().getSuperclass();
				Method metodos[] = c.getMethods();		
				Class[] parameterTypes = null;
				for(int i=0; (i < metodos.length); i++){
					Method m = metodos[i];			
					Object[] arguments = new Object[] {};
					parameterTypes = m.getParameterTypes();		
					if (((parameterTypes == null) || (parameterTypes.length == 0)) && (m.getName().startsWith("get")) && !(Collection.class.isAssignableFrom(m.getReturnType()))){
						Object retorno = m.invoke(oPersistente,arguments);
						String nombreSet = m.getName().replaceFirst("g","s");
						@SuppressWarnings("unchecked")
						Method seteo = c.getMethod(nombreSet,retorno.getClass());
						seteo.invoke(oPersistente,retorno);
					}

				}		 
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

}